package com.example.coroutines

import kotlinx.coroutines.*
import org.junit.Test
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.system.measureTimeMillis


/**
 * 协程测试
 * 什么是协程
 * 协程本质上是轻量级线程
 *
 * 协程好处？
 * 1）协程让异步逻辑同步化，杜绝回调地狱
 * 2）协程最核心的特点，函数或者一段程序能够被挂起，
 * 稍后再在挂起的位置恢复
 *
 * 挂起和阻塞的区别：
 * 挂起：用户U请求执行时A任务，未获取到对应的资源，记录挂起点，开始处理其他的任务，
 *      当资源已经获取到时，从挂起点继续执行任务A
 * 阻塞：用户U请求执行时A任务，未获取到对应的资源，记录挂起点，
 *      一直等待资源，不处理其他任务。只有获取到资源时才继续执行任务A
 *
 * @author zhouronghua
 * @time 2021/9/29 8:52 上午
 */
class CoroutinesTest {

    /**
     * 测试协程的创建
     *
     * @author zhouronghua
     * @time 2021/9/29 8:55 上午
     */
    @Test
    fun testCreateCoroutines() {
        GlobalScope.launch {
            // 在后台启动一个新的协程并继续(非主协程)
            delay(1000L) // 非阻塞的等待 1 秒钟（默认时间单位是毫秒）
            println("World! ${Thread.currentThread().id}") // 在延迟后打印输出

        }
        println("Hello, ${Thread.currentThread().id}") // 协程已在等待时主线程还在继续
        Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
    }

    /**
     * 测试阻塞主线程
     * 使用runBlocking阻塞主协程，达到Thread.sleep阻塞主协程效果
     * runBlocking 的主协程会一直 阻塞 直到 runBlocking 内部的协程执行完毕。
     *
     * 打印结果
     * Hello, start 12 1632907400042
     * World! 15 1632907403063
     * Hello, end 12 1632907405049
     *
     * @author zhouronghua
     * @time 2021/9/29 5:16 下午
     */
    @Test
    fun testBlockMainThread() {
        GlobalScope.launch {
            // 在后台启动一个新的协程并继续(非主协程)
            delay(3000L) // 非阻塞的等待 1 秒钟（默认时间单位是毫秒）
            println("World! ${Thread.currentThread().id} ${System.currentTimeMillis()}") // 在延迟后打印输出

        }
        // 但是这个表达式阻塞了主协程
        runBlocking {
            // 此处直接打印
            println("Hello, start ${Thread.currentThread().id} ${System.currentTimeMillis()}") // 协程已在等待时主线程还在继续
            delay(5000L) // ……我们延迟 5 秒来保证 JVM 的存活
            // 但是这个表达式阻塞了主线程(5秒以后才会打印Hello)
            println("Hello, end ${Thread.currentThread().id} ${System.currentTimeMillis()}") // 协程已在等待时主线程还在继续
        }
    }

    /**
     * 测试等待子协程处理完成
     * 打印结果：
     * main hello
     * wait child
     * child World!
     * wait child end
     * @author zhouronghua
     * @time 2021/9/29 5:30 下午
     */
    @Test
    fun testWaitChildCoroutines() {
        println("=== 测试等待子协程 ===")
        val job = GlobalScope.launch {
            // 在后台启动一个新的协程并继续(非主协程)
            delay(1000L) // 非阻塞的等待 1 秒钟（默认时间单位是毫秒）
            println("child World!") // 在延迟后打印输出
        }
        println("main hello")
        runBlocking {
            job.run {
                // 等待子协程处理完成
                println("wait child")
                join()
                println("wait child end")
            }
        }
    }

    /**
     * 使用 coroutineScope 构建器声明自己的作用域
     * 它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。
     * runBlocking 与 coroutineScope 可能看起来很类似，因为它们都会等待其协程体以及所有子协程结束。
     * 主要区别在于，runBlocking 方法会阻塞当前线程来等待，
     * 而 coroutineScope 只是挂起，会释放底层线程用于其他用途。
     * 由于存在这点差异，runBlocking 是常规函数，而 coroutineScope 是挂起函数。
     *
     * @author zhouronghua
     * @time 2021/9/29 5:47 下午
     */
    @Test
    fun testCoroutineScope() {
        println("=== 测试CoroutineScope ===")
        runBlocking {
            launch {
                delay(200L)
                println("Task from runBlocking")
            }

            coroutineScope {
                // 创建一个协程作用域
                launch {
                    delay(500L)
                    println("Task from nested launch")
                }
                delay(100L)
                println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
            }
            // 这一行在内嵌 launch 执行完毕后才输出
            println("Coroutine scope is over")
        }
    }

    /**
     * 这是你的第一个挂起函数
     * 在协程内部可以像普通函数一样使用挂起函数， 不过其额外特性是，
     * 同样可以使用其他挂起函数（如本例中的 delay）来挂起协程的执行。
     *
     * @author zhouronghua
     * @time 2021/9/29 6:15 下午
     */
    suspend fun doWorld() {
        delay(1000L)
        println("World!")
    }

    /**
     * 测试使用挂起函数
     * 说明: 挂起函数需要在协程中调用
     *
     * @author zhouronghua
     * @time 2021/9/29 6:16 下午
     */
    @Test
    fun testBlockFunction() {
        runBlocking {
            launch { doWorld() }
            println("Hello,")
        }
    }

    /**
     * 测试协程启动Launch和async
     * 说明: 挂起函数需要在协程中调用
     *
     * @author zhouronghua
     * @time 2021/12/28 8:48 上午
     */
    @Test
    fun testLaunchAsync() {
        runBlocking {
            val job1 = launch {

            }


        }
    }

    /**
     * 协程是很轻量的线程
     * 协程上下文中的作业
     * coroutineContext [Job]：协程的 Job 是上下文的一部分，
     * 并且可以使用 coroutineContext [Job] 表达式在上下文中检索它：
     *
     * @author zhouronghua
     * @time 2021/9/29 7:13 下午
     */
    @Test
    fun testMultiCoroutine() {
        println("=== 测试多个协程 === ${Thread.currentThread().name}")
        runBlocking {
            println("=== 主线程协程 === ${Thread.currentThread().name}")
            // 这些协程任务会在同一个线程输出
            repeat(100_000) {
                // 启动大量的协程
                val job = launch {
                    delay(5000L)
                    println(". ${Thread.currentThread().id}")
                }
            }
            // My job is "coroutine#1":BlockingCoroutine{Active}@ff2f63f
            println("My job is ${coroutineContext[Job]}")
        }
//        // 线程会导致内存不足，输入不了内容
//        println("=== 测试多个线程 ===")
//        (1..1000_000).forEach {
//            // 启动大量的协程
//            Thread(Runnable {
//                print(".")
//            }).start()
//        }
//        Thread.sleep(9000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
    }

    /**
     * 在实践中绝大多数取消一个协程的理由是它有可能超时。
     * 当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪，
     * 这里已经准备好使用 withTimeout 函数来做这件事。
     * 超时以后会抛出TimeoutCancellationException异常
     *
     * @author zhouronghua
     * @time 2021/10/9 8:46 上午
     */
    @Test
    fun testCoroutineTieemout() {
        println("=== 测试协程超时 ===")
        runBlocking {
            withTimeout(1300L) {
                repeat(1000, { it ->
                    println("I am sleep $it")
                    delay(300L)
                })
            }
        }
    }

    /**
     * 测试创建协程
     */
    @Test
    fun testCreateCoroutine() {
        val continuation = suspend {
            // 执行协程任务返回执行结果
            println("In coroutine")
            delay(5000)
            // 返回值为5
            5
        }.startCoroutine(object : Continuation<Int> {
            override val context: CoroutineContext = EmptyCoroutineContext

            override fun resumeWith(result: Result<Int>) {
                println("resume Coroutine " + result)
            }
        })
    }

    suspend fun doOne(): Int {
        delay(1000)
        return 14
    }

    suspend fun doTwo(): Int {
        delay(1000)
        return 25
    }

    /**
     * 测试两个协程任务组合执行
     * result is 39
     * total use time 2016
     *
     * @author zhouronghua
     * @time 2021/12/28 7:52 下午
     */
    @Test
    fun testSyncCombine() = runBlocking {
        val time = measureTimeMillis {
            // 同步启动一个挂起任务
            val one = doOne()
            // 同步启动第二个挂起任务
            val two = doTwo()
            // 两个任务一次执行后才打印结果
            println("result is ${one + two}")
        }
        println("total use time $time")
    }

    /**
     * 测试两个异步协程任务组合执行
     * result is 39
     *  total use time 1024
     *  执行时间远远小于同步执行时间
     *
     * @author zhouronghua
     * @time 2021/12/28 7:52 下午
     */
    @Test
    fun testAsyncCombine() = runBlocking {
        val time = measureTimeMillis {
            // 同步启动一个挂起任务
            val one = async { doOne() }
            // 同步启动第二个挂起任务
            val two = async { doTwo() }
            // 等待两个异步任务执行后才打印结果
            println("result is ${one.await() + two.await()}")
        }
        println("total use time $time")
    }
}